//==============================================================================
// Copyright (c) 2008-2013 Niall Ryan. All Rights Reserved.
//==============================================================================

#ifndef HYDRA_ATOMIC_ATOMIC_H
#define HYDRA_ATOMIC_ATOMIC_H 1

namespace Hydra
{
	/**
	 * Most operations on Atomic types can specify the memory ordering semantics to use, and
	 * this enum specifies the different semantics available. By default, loads will have
	 * acquire semantics, stores have release semantics, and compareAndSwap and other
	 * operations have a full fence.
	 */
	enum FenceType
	{
		/**
		 * A memory operation with acquire semantics will prevent other memory operations from being
		 * moved before it. Earlier memory operations are allowed to move after it however.
		 */
		Acquire,

		/**
		 * A memory operation with release semantics will prevent other memory operations from being
		 * moved after it. Later memory operations are allowed to move before it however.
		 */
		Release,

		/**
		 * A memory operation using a full fence will prevent other memory operations from being
		 * moved across it in either direction.
		 */
		FullFence,

		/**
		 * A memory operation with no fence imposes no memory ordering semantics. It will allow
		 * other memory operations to move both before and after it.
		 */
		NoFence
	};
}

#include "internal/atomic_internal.h"

namespace Hydra
{
	/**
	 * Wraps a type so that it can be accessed atomically. Works on any type with a size less than or equal to 8 bytes. The
	 * type will be automatically aligned and padded if necessary. Different memory semantics can be specified for each
	 * operation - by default loads will use acquire semantics and writes use release semantics, all other operations
	 * use a full fence.
	 */
	template<typename T>
	class Atomic
	{
		typedef Internal::AtomicStorage<T, sizeof(T)> Storage;
		typedef typename Storage::AtomicType AtomicType;
		template<FenceType F> struct LoadStoreHelper : public Internal::LoadStoreHelper<AtomicType, sizeof(AtomicType), F> { };
		template<FenceType F> struct AtomicOpHelper : public Internal::AtomicOpHelper<AtomicType, T, sizeof(AtomicType), F> { };

	public:
		template<FenceType F>
		const T load() const;

		template<FenceType F>
		void store(const T& value);

		operator const T() const;
		const T operator=(const T& rhs);

		const T operator->();

		const T compareAndSwap(const T& value, const T& compare);
		const T getAndSet(const T& value);
		const T getAndAdd(const T& value);
		const T getAndIncrement();
		const T getAndDecrement();

		const T operator++();
		const T operator++(int);
		const T operator--();
		const T operator--(int);

	protected:
		Storage m_storage;
	};

	//====================================================================================================================
	//====================================================================================================================
	//====================================================================================================================

	template<typename T>
	template<FenceType F>
	inline const T Atomic<T>::load() const
	{
		return Storage::fromAtomicType(LoadStoreHelper<F>::load(m_storage.getPtr()));
	}

	template<typename T>
	template<FenceType F>
	inline void Atomic<T>::store(const T& value)
	{
		LoadStoreHelper<F>::store(m_storage.getPtr(), Storage::toAtomicType(value));
	}

	template<typename T>
	inline Atomic<T>::operator const T() const
	{
		return load<Acquire>();
	}

	template<typename T>
	inline const T Atomic<T>::operator=(const T& rhs)
	{
		store<Release>(rhs);
		return rhs;
	}

	template<typename T>
	inline const T Atomic<T>::operator->()
	{
		return load<Acquire>();
	}

	template<typename T>
	inline const T Atomic<T>::compareAndSwap(const T& value, const T& compare)
	{
		AtomicType atomicValue = Storage::toAtomicType(value);
		AtomicType atomicCompare = Storage::toAtomicType(compare);
		return Storage::fromAtomicType(AtomicOpHelper<FullFence>::compareAndSwap(m_storage.getPtr(), atomicValue, atomicCompare));
	}

	template<typename T>
	inline const T Atomic<T>::getAndSet(const T& value)
	{
		AtomicType atomicValue = Storage::toAtomicType(value);
		return Storage::fromAtomicType(AtomicOpHelper<FullFence>::getAndSet(m_storage.getPtr(), atomicValue));
	}

	template<typename T>
	inline const T Atomic<T>::getAndAdd(const T& value)
	{
		AtomicType atomicValue = Storage::toAtomicType(value);
		return Storage::fromAtomicType(AtomicOpHelper<FullFence>::getAndAdd(m_storage.getPtr(), atomicValue));
	}

	template<typename T>
	inline const T Atomic<T>::getAndIncrement()
	{
		return Storage::fromAtomicType(AtomicOpHelper<FullFence>::getAndIncrement(m_storage.getPtr()));
	}

	template<typename T>
	inline const T Atomic<T>::getAndDecrement()
	{
		return Storage::fromAtomicType(AtomicOpHelper<FullFence>::getAndDecrement(m_storage.getPtr()));
	}

	template<typename T>
	inline const T Atomic<T>::operator++()
	{
		return getAndIncrement()+1;
	}

	template<typename T>
	inline const T Atomic<T>::operator++(int)
	{
		return getAndIncrement();
	}

	template<typename T>
	inline const T Atomic<T>::operator--()
	{
		return getAndDecrement()-1;
	}

	template<typename T>
	inline const T Atomic<T>::operator--(int)
	{
		return getAndDecrement();
	}
}

#endif
